using UnityEngine;
#if UNITY_EDITOR	
using UnityEditor;
#endif
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine.SceneManagement;
using System;

/*
 	In this demo, we demonstrate:
	1.	Automatic asset bundle dependency resolving & loading.
		It shows how to use the manifest assetbundle like how to get the dependencies etc.
	2.	Automatic unloading of asset bundles (When an asset bundle or a dependency thereof is no longer needed, the asset bundle is unloaded)
	3.	Editor simulation. A bool defines if we load asset bundles from the project or are actually using asset bundles(doesn't work with assetbundle variants for now.)
		With this, you can player in editor mode without actually building the assetBundles.
	4.	Optional setup where to download all asset bundles
	5.	Build pipeline build postprocessor, integration so that building a player builds the asset bundles and puts them into the player data (Default implmenetation for loading assetbundles from disk on any platform)
	6.	Use WWW.LoadFromCacheOrDownload and feed 128 bit hash to it when downloading via web
		You can get the hash from the manifest assetbundle.
	7.	AssetBundle variants. A prioritized list of variants that should be used if the asset bundle with that variant exists, first variant in the list is the most preferred etc.
*/

public class LoadedAssetBundle
{
    public AssetBundle m_AssetBundle;
    public int m_ReferencedCount;

    public LoadedAssetBundle(AssetBundle assetBundle)
    {
        Debug.LogError("New LoadedAssetBundle : " + assetBundle.name);
        m_AssetBundle = assetBundle;
        m_ReferencedCount = 1;
    }
}

public class AssetBundleManager : TSingleton<AssetBundleManager>
{
    static private AssetBundleManifest m_AssetBundleManifest = null;
    /// <summary>
    /// 资源清单文件
    /// </summary>
    static public AssetBundleManifest AssetBundleManifestObject
    {
        set { m_AssetBundleManifest = value; }
    }

#if UNITY_EDITOR
    private const string kSimulateAssetBundles = "SimulateAssetBundles";
    static private int m_SimulateAssetBundleInEditor = -1;
    /// <summary>
    /// 模拟标识
    /// </summary>
    static public bool SimulateAssetBundleInEditor
    {
        get
        {
            Animation a = new Animation();
            AnimationClip cp = new AnimationClip();
            Animator at = new Animator();


            if (m_SimulateAssetBundleInEditor == -1)
                m_SimulateAssetBundleInEditor = EditorPrefs.GetBool(kSimulateAssetBundles, true) ? 1 : 0;

            return m_SimulateAssetBundleInEditor != 0;
        }
        set
        {
            int newValue = value ? 1 : 0;
            if (newValue != m_SimulateAssetBundleInEditor)
            {
                m_SimulateAssetBundleInEditor = newValue;
                EditorPrefs.SetBool(kSimulateAssetBundles, value);
            }
        }
    }
#endif

    //---------------------------------------------------------------------------------------------------------------------

    private string[] m_ActiveVariants = { };

    /// <summary>
    /// 已加载Bundle
    /// </summary>
    private Dictionary<string, LoadedAssetBundle> m_LoadedAssetBundles = new Dictionary<string, LoadedAssetBundle>();

    /// <summary>
    /// 下载中资源列表
    /// </summary>
    private Dictionary<string, WWW> m_DownloadingWWWs = new Dictionary<string, WWW>();
    private Dictionary<string, string> m_DownloadingErrors = new Dictionary<string, string>();

    /// <summary>
    /// 已加载资源的依赖集
    /// </summary>
    private Dictionary<string, string[]> m_Dependencies = new Dictionary<string, string[]>();

    /// <summary>
    /// 已加载资源缓存
    /// </summary>
    public Dictionary<string, LoadedAssetBundle> LoadedAssetBundles
    {
        get
        {
            return m_LoadedAssetBundles;
        }
    }

    //---------------------------------------------------------------------------------------------------------------------

    protected override void Awake()
    {
        base.Awake();
        if (TSingleton<AssetBundleManager>.Singleton == this)
        {
            Initialize();
        }
    }

    protected override void SingletonStart()
    {
        base.SingletonStart();

        //if (IsSingletonCreated)
        //{
        //    //做资源预加载后
        //    EventCenterSystem.DespatchEvent(EventKeyDefine.MSG_ABInited);
        //}
    }

    //--公开函数-------------------------------------------------------------------------------------------------------------------

    /// <summary>
    /// 初始化AB管理器
    /// </summary>
    public bool Initialize()
    {
        string manifestAssetBundleName = GetPlatformName();

#if UNITY_EDITOR
        Debug.Log("Simulation Mode: " + (SimulateAssetBundleInEditor ? "Enabled" : "Disabled"));
        if (SimulateAssetBundleInEditor) return true;
#endif
        if (LoadAssetBundleInternal(manifestAssetBundleName))
        {
            LoadedAssetBundle bundle;
            if (m_LoadedAssetBundles.TryGetValue(manifestAssetBundleName, out bundle))
            {
                m_AssetBundleManifest = bundle.m_AssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            }
            if (m_AssetBundleManifest != null)
            {
                return true;
            }
        }

        Debug.LogError("Load AssetBundleManifest Error!");

        return false;
    }

    /// <summary>
    /// 同步加载资源
    /// </summary>
    public T LoadAsset<T>(string assetBundleName, string assetName) where T : UnityEngine.Object
    {
        assetBundleName = assetBundleName.ToLower();
        if (typeof(T) == typeof(GameObject))
        {
            assetName = assetName.Replace(".prefab", "") + ".prefab";
        }
#if UNITY_EDITOR
        if (SimulateAssetBundleInEditor)
        {
            string assetNameWithOutExt = Path.GetFileNameWithoutExtension(assetName);
            string[] assetPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(assetBundleName, assetNameWithOutExt);

            if (assetPaths == null || assetPaths.Length == 0)
            {
                Debug.LogError("There is no asset with name \"" + assetName + "\" in " + assetBundleName);
                return null;
            }

            string assetPath = assetPaths[0];
            for (int i = 0; i < assetPaths.Length; i++)
            {
                if (Path.GetFileName(assetPaths[i]) == assetName)
                {
                    assetPath = assetPaths[i];
                    break;
                }
            }

            T target = AssetDatabase.LoadAssetAtPath<T>(assetPath);
            return target;
        }
        else
#endif
        {
            if (LoadAssetBundle(assetBundleName))
            {
                LoadedAssetBundle bundle;
                if (m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle))
                {
                    return bundle.m_AssetBundle.LoadAsset<T>(assetName);
                }
            }
        }
        return null;
    }

    /// <summary>
    /// 异步加载资源
    /// </summary>
    public void LoadAssetAsync<T>(string assetBundleName, string assetName, Action<T> action) where T : UnityEngine.Object
    {
        assetBundleName = assetBundleName.ToLower();
        if (typeof(T) == typeof(GameObject))
        {
            assetName = assetName.Replace(".prefab", "") + ".prefab";
        }

#if UNITY_EDITOR
        if (SimulateAssetBundleInEditor)
        {
            string assetNameWithOutExt = Path.GetFileNameWithoutExtension(assetName);
            string[] assetPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(assetBundleName, assetNameWithOutExt);
            if (assetPaths.Length == 0)
            {
                Debug.LogError("There is no asset with name \"" + assetName + "\" in " + assetBundleName);
            }

            string assetPath = assetPaths[0];
            for (int i = 0; i < assetPaths.Length; i++)
            {
                if (Path.GetFileName(assetPaths[i]) == assetName)
                {
                    assetPath = assetPaths[i];
                    break;
                }
            }

            T target = AssetDatabase.LoadAssetAtPath<T>(assetPath);

            if (action != null) action(target);
        }
        else
#endif
        {
            assetBundleName = RemapVariantName(assetBundleName);

            this.StartCoroutine(LoadAssetBundleAsync(assetBundleName, () =>
            {
                LoadedAssetBundle bundle = null;
                AssetBundleRequest m_Operation = null;

                if (m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle) && bundle != null && action != null)
                {
                    m_Operation = bundle.m_AssetBundle.LoadAssetAsync<T>(assetName);
                    m_Operation.completed += (operation) =>
                    {
                        action(m_Operation.asset as T);
                    };
                }
            }));
        }
    }

    /// <summary>
    /// 异步加载场景
    /// </summary>
    public void LoadLevelAsync(string assetBundleName, string levelName, bool isAdditive = false)
    {
        assetBundleName = assetBundleName + "/" + levelName;
        assetBundleName = assetBundleName.ToLower();

        AsyncOperation m_Operation = null;

#if UNITY_EDITOR
        if (SimulateAssetBundleInEditor)
        {
            string[] levelPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(assetBundleName, levelName);
            if (levelPaths.Length == 0)
            {
                Debug.LogError("There is no scene with name \"" + levelName + "\" in " + assetBundleName);
                return;
            }

            if (isAdditive)
                m_Operation = EditorApplication.LoadLevelAdditiveAsyncInPlayMode(levelPaths[0]);
            else
                m_Operation = EditorApplication.LoadLevelAsyncInPlayMode(levelPaths[0]);
        }
        else
#endif
        {
            string loadedError = "";
            LoadedAssetBundle bundle = Singleton.GetLoadedAssetBundle(assetBundleName, out loadedError);

            if (!string.IsNullOrEmpty(loadedError))
            {
                Debug.LogError(loadedError);
                return;
            }

            assetBundleName = RemapVariantName(assetBundleName);
            this.StartCoroutine(LoadAssetBundleAsync(assetBundleName, null));

            if (isAdditive)
                m_Operation = SceneManager.LoadSceneAsync(levelName, LoadSceneMode.Additive);
            else
                m_Operation = SceneManager.LoadSceneAsync(levelName);
        }

        m_Operation.completed += (operation) =>
        {
            EventCenterSystem<string>.DespatchEvent(EventKeyDefine.MSG_LevelLoadComplete, levelName);
        };
    }

    /// <summary>
    /// 卸载AB
    /// </summary>
    public void UnloadAssetBundle(string assetBundleName)
    {
        assetBundleName = assetBundleName.ToLower();
#if UNITY_EDITOR
        if (SimulateAssetBundleInEditor)
        {
            // UnityEngine.Debug.Log("If we're in Editor simulation mode, we don't have to load the manifest assetBundle.");
            return;
        }
#endif

        UnloadAssetBundleInternal(assetBundleName);
        UnloadDependencies(assetBundleName);
    }

    /// <summary>
    /// 获取已加载AB
    /// </summary>
    /// <param name="assetBundleName"></param>
    /// <param name="error"></param>
    /// <returns></returns>
    public LoadedAssetBundle GetLoadedAssetBundle(string assetBundleName, out string error)
    {
        if (m_DownloadingErrors.TryGetValue(assetBundleName, out error))
            return null;

        LoadedAssetBundle bundle = null;
        m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
        if (bundle == null)
            return null;

        // No dependencies are recorded, only the bundle itself is required.
        string[] dependencies = null;
        if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies))
            return bundle;

        // Make sure all dependencies are loaded
        foreach (var dependency in dependencies)
        {
            if (m_DownloadingErrors.TryGetValue(assetBundleName, out error))
                return bundle;

            // Wait all the dependent assetBundles being loaded.
            LoadedAssetBundle dependentBundle;
            m_LoadedAssetBundles.TryGetValue(dependency, out dependentBundle);
            if (dependentBundle == null)
                return null;
        }

        return bundle;
    }

    /// <summary>
    /// 获取给定AB名字中所有资源名
    /// </summary>
    public string[] GetAllAssetsNameFromBundle(string assetBundleName)
    {
        assetBundleName = assetBundleName.ToLower();

#if UNITY_EDITOR
        if (SimulateAssetBundleInEditor)
        {
            string[] paths = AssetDatabase.GetAssetPathsFromAssetBundle(assetBundleName);
            for (int i = 0; i < paths.Length; i++)
            {
                paths[i] = System.IO.Path.GetFileNameWithoutExtension(paths[i]);
            }
            return paths;
        }
        else
#endif
        {
            if (LoadAssetBundle(assetBundleName))
            {
                LoadedAssetBundle bundle;
                if (m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle))
                {
                    string[] paths = bundle.m_AssetBundle.GetAllAssetNames();
                    for (int i = 0; i < paths.Length; i++)
                    {
                        paths[i] = System.IO.Path.GetFileNameWithoutExtension(paths[i]);
                    }
                    return paths;
                }
            }
            return new string[0];
        }
    }

    //--工具函数-------------------------------------------------------------------------------------------------------------------

    protected bool LoadAssetBundle(string assetBundleName)
    {
#if UNITY_EDITOR
        // If we're in Editor simulation mode, we don't have to really load the assetBundle and its dependencies.
        if (SimulateAssetBundleInEditor)
            return true;
#endif
        if (m_AssetBundleManifest == null)
        {
            Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
            return false;
        }

        // Check if the assetBundle has already been processed.
        bool isLoadSuccess = LoadAssetBundleInternal(assetBundleName);

        // Load dependencies.
        if (isLoadSuccess)
            isLoadSuccess = LoadDependencies(assetBundleName);
        return isLoadSuccess;
    }

    protected bool LoadDependencies(string assetBundleName)
    {
        if (m_AssetBundleManifest == null)
        {
            Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
            return false;
        }

        // Get dependecies from the AssetBundleManifest object..
        string[] dependencies = m_AssetBundleManifest.GetAllDependencies(assetBundleName);
        if (dependencies.Length == 0)
            return true;

        for (int i = 0; i < dependencies.Length; i++)
        {
            dependencies[i] = RemapVariantName(dependencies[i]);
        }

        // Record and load all dependencies.
        if (!m_Dependencies.ContainsKey(assetBundleName))
        {
            m_Dependencies.Add(assetBundleName, dependencies);
        }

        for (int i = 0; i < dependencies.Length; i++)
        {
            if (!LoadAssetBundleInternal(dependencies[i]))
                return false;
        }
        return true;
    }

    protected bool LoadAssetBundleInternal(string assetBundleName)
    {
        // Already loaded.
        LoadedAssetBundle bundle = null;
        m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
        if (bundle != null)
        {
            bundle.m_ReferencedCount++;
            return true;
        }

        string bundlePath = PersistentBundlePath(assetBundleName);
        FileInfo fileInfo = new FileInfo(bundlePath);
        if (!fileInfo.Exists)
        {
            bundlePath = StreamingBundlePath(assetBundleName);
        }

        try
        {
            bundle = new LoadedAssetBundle(AssetBundle.LoadFromFile(bundlePath));
            bundle.m_ReferencedCount = 1;
            m_LoadedAssetBundles.Add(assetBundleName, bundle);
            return true;
        }
        catch (System.Exception e)
        {
            Debug.LogException(e);
            m_DownloadingErrors.Add(assetBundleName, e.Message);
            return false;
        }
    }

    protected IEnumerator LoadAssetBundleAsync(string assetBundleName, Action action)
    {

#if UNITY_EDITOR
        // If we're in Editor simulation mode, we don't have to really load the assetBundle and its dependencies.
        if (SimulateAssetBundleInEditor)
            yield break;
#endif
        if (m_AssetBundleManifest == null)
        {
            Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
            yield break;
        }

        yield return LoadAssetBundleAsyncInternal(assetBundleName);

        LoadDependenciesAsync(assetBundleName);

        if (action != null) action();
    }

    protected IEnumerator LoadDependenciesAsync(string assetBundleName)
    {
        if (m_AssetBundleManifest == null)
        {
            Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
            yield break;
        }

        // Get dependecies from the AssetBundleManifest object..
        string[] dependencies = m_AssetBundleManifest.GetAllDependencies(assetBundleName);
        if (dependencies.Length == 0)
        {
            yield break;
        }

        for (int i = 0; i < dependencies.Length; i++)
        {
            dependencies[i] = RemapVariantName(dependencies[i]);
        }

        // Record and load all dependencies.
        m_Dependencies.Add(assetBundleName, dependencies);
        for (int i = 0; i < dependencies.Length; i++)
        {
            LoadAssetBundleAsyncInternal(dependencies[i]);
        }
    }

    protected IEnumerator LoadAssetBundleAsyncInternal(string assetBundleName)
    {
        // Already loaded.
        LoadedAssetBundle bundle = null;
        m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
        if (bundle != null)
        {
            bundle.m_ReferencedCount++;
            yield break;
        }

        // Error loaded.
        string[] bundles = m_AssetBundleManifest.GetAllAssetBundles();
        if (System.Array.IndexOf(bundles, assetBundleName) == -1)
        {
            m_DownloadingErrors.Add(assetBundleName, "no such assetBundleName with:" + assetBundleName);
            yield break;
        }

        // Already loading.
        if (m_DownloadingWWWs.ContainsKey(assetBundleName))
        {
            yield return m_DownloadingWWWs[assetBundleName];
        }

        WWW download = null;

        string url = PersistentBundleURI(assetBundleName);

        Debug.LogError("LoadAssetBundleAsyncInternal_url: " + url);

        download = WWW.LoadFromCacheOrDownload(url, m_AssetBundleManifest.GetAssetBundleHash(assetBundleName), 0);
        m_DownloadingWWWs.Add(assetBundleName, download);

        yield return download;

        if (!string.IsNullOrEmpty(download.error))
        {
            string errorInfo = string.Format("Failed downloading bundle {0} from {1}: {2}", assetBundleName, download.url, download.error);
            Debug.LogError(errorInfo);
            m_DownloadingErrors.Add(assetBundleName, errorInfo);
        }

        if (download.isDone)
        {
            try
            {
                bundle = new LoadedAssetBundle(download.assetBundle);
                bundle.m_ReferencedCount = 1;
                m_LoadedAssetBundles.Add(assetBundleName, bundle);
            }
            catch (System.Exception e)
            {
                Debug.LogException(e);
                m_DownloadingErrors.Add(assetBundleName, string.Format("{0} is not a valid asset bundle.", assetBundleName));
            }
        }

        m_DownloadingWWWs.Remove(assetBundleName);
    }

    protected void UnloadDependencies(string assetBundleName)
    {
        string[] dependencies = null;
        if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies))
            return;

        // Loop dependencies.
        foreach (var dependency in dependencies)
        {
            UnloadAssetBundleInternal(dependency);
        }

        m_Dependencies.Remove(assetBundleName);
    }

    protected void UnloadAssetBundleInternal(string assetBundleName)
    {
        string error;
        LoadedAssetBundle bundle = GetLoadedAssetBundle(assetBundleName, out error);
        if (bundle == null)
            return;

        if (--bundle.m_ReferencedCount == 0)
        {
            bundle.m_AssetBundle.Unload(false);
            m_LoadedAssetBundles.Remove(assetBundleName);

            Debug.Log(assetBundleName + " has been unloaded successfully");
        }
    }

    // Remaps the asset bundle name to the best fitting asset bundle variant.
    protected string RemapVariantName(string assetBundleName)
    {
        string[] bundlesWithVariant = m_AssetBundleManifest.GetAllAssetBundlesWithVariant();

        string[] split = assetBundleName.Split('.');

        int bestFit = int.MaxValue;
        int bestFitIndex = -1;
        // Loop all the assetBundles with variant to find the best fit variant assetBundle.
        for (int i = 0; i < bundlesWithVariant.Length; i++)
        {
            string[] curSplit = bundlesWithVariant[i].Split('.');
            if (curSplit[0] != split[0])
                continue;

            int found = System.Array.IndexOf(m_ActiveVariants, curSplit[1]);

            // If there is no active variant found. We still want to use the first 
            if (found == -1)
                found = int.MaxValue - 1;

            if (found < bestFit)
            {
                bestFit = found;
                bestFitIndex = i;
            }
        }

        if (bestFit == int.MaxValue - 1)
        {
            Debug.LogWarning("Ambigious asset bundle variant chosen because there was no matching active variant: " + bundlesWithVariant[bestFitIndex]);
        }

        if (bestFitIndex != -1)
        {
            return bundlesWithVariant[bestFitIndex];
        }
        else
        {
            return assetBundleName;
        }
    }

    //--路径相关-------------------------------------------------------------------------------------------------------------------

    public string StreamingBundleURI(string bundleName)
    {
        if (Application.isEditor)
        {
            string path = Path.Combine(Application.streamingAssetsPath, GetPlatformName(), bundleName);
            return "file:///" + path.Replace("\\", "/");
        }
        else if (Application.isMobilePlatform || Application.isConsolePlatform)
        {
            return Path.Combine(Application.streamingAssetsPath, GetPlatformName(), bundleName);
        }
        else
        { // For standalone player.
            return "file://" + Path.Combine(Application.streamingAssetsPath, GetPlatformName(), bundleName);
        }
    }

    public string StreamingBundlePath(string bundleName)
    {
        string path = Path.Combine(Application.streamingAssetsPath, GetPlatformName(), bundleName);
        return path.Replace("\\", "/");
    }

    /// <summary>
    /// 本地持久化路径的URL
    /// </summary>
    public string PersistentBundleURI(string bundleName)
    {
        string path = PersistentBundlePath(bundleName);

        if (Application.isEditor)
        {
            return "file:///" + path;
        }
        else
        {
            return "file://" + path;
        }
    }

    /// <summary>
    /// 本地持久化路径
    /// </summary>
    public string PersistentBundlePath(string bundleName)
    {
        return Path.Combine(PersistentBundleHome, bundleName).Replace("\\", "/");
    }

    /// <summary>
    /// 本地持久化路径_根目录
    /// </summary>
    public string PersistentBundleHome
    {
        get { return Application.persistentDataPath + "/Bundles"; }
    }

    /// <summary>
    /// 获取平台名字
    /// </summary>
    public string GetPlatformName()
    {
#if UNITY_EDITOR
        return GetPlatformForAssetBundles(EditorUserBuildSettings.activeBuildTarget);
#else
        return GetPlatformForAssetBundles(Application.platform);
#endif
    }

#if UNITY_EDITOR
    private string GetPlatformForAssetBundles(BuildTarget target)
    {
        switch (target)
        {
            case BuildTarget.Android:
                return "Android";
            case BuildTarget.iOS:
                return "iOS";
            case BuildTarget.WebGL:
                return "WebGL";
            case BuildTarget.StandaloneWindows:
            case BuildTarget.StandaloneWindows64:
                return "Windows";
            case BuildTarget.StandaloneOSXIntel:
            case BuildTarget.StandaloneOSXIntel64:
            case BuildTarget.StandaloneOSX:
                return "OSX";
            default:
                return null;
        }
    }
#endif

    private string GetPlatformForAssetBundles(RuntimePlatform platform)
    {
        switch (platform)
        {
            case RuntimePlatform.Android:
                return "Android";
            case RuntimePlatform.IPhonePlayer:
                return "iOS";
            case RuntimePlatform.WebGLPlayer:
                return "WebGL";
            //case RuntimePlatform.OSXWebPlayer:
            //case RuntimePlatform.WindowsWebPlayer:
            //    return "WebPlayer";
            case RuntimePlatform.WindowsPlayer:
                return "Windows";
            case RuntimePlatform.OSXPlayer:
                return "OSX";
            // Add more build targets for your own.
            // If you add more targets, don't forget to add the same platforms to GetPlatformForAssetBundles(RuntimePlatform) function.
            default:
                return null;
        }
    }

}
